package com.evil.application.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.evil.application.api.RemoteRobotActionService;
import com.evil.application.pojo.dto.order.*;
import com.evil.application.pojo.dto.order.data.OrderControlDataRespDTO;
import com.evil.application.pojo.entity.ApplicationRobotLogDetail;
import com.evil.application.service.*;
import com.evil.application.util.ApplicationUtil;
import com.evil.application.util.MatchingUtil;
import com.evil.application.util.OrderDataUtil;
import com.evil.application.util.ReTriggerUtil;
import com.evil.common.account.dto.user.UserBaseRespDTO;
import com.evil.common.account.dto.user.UserEnterpriseDTO;
import com.evil.common.account.util.LoginUtil;
import com.evil.common.application.constant.ApplicationRedisConstant;
import com.evil.common.application.dto.judge.JudgeOr;
import com.evil.common.application.dto.mq.RobotActionMQReqDTO;
import com.evil.common.application.dto.order.OrderPageDataRespDTO;
import com.evil.common.application.dto.order.data.ControlDataDTO;
import com.evil.common.application.dto.robot.RobotRuleDTO;
import com.evil.common.application.dto.robot.action.RobotActionReqDTO;
import com.evil.common.application.enums.order.OrderProcessStatusEnum;
import com.evil.common.application.enums.order.OrderSourceTypeEnum;
import com.evil.common.application.enums.robot.RobotActionResultEnum;
import com.evil.common.application.enums.robot.RobotTriggerTypeEnum;
import com.evil.common.application.util.ApplicationRedisKeyUtil;
import com.evil.common.core.enums.RCodeEnum;
import com.evil.common.core.enums.SwitchEnum;
import com.evil.common.core.exception.BusinessException;
import com.evil.common.core.function.Callback;
import com.evil.common.core.util.RedisUtil;
import com.evil.common.core.util.StreamUtil;
import com.evil.common.log.annotation.ExecuteTime;
import com.evil.common.log.function.ExecuteTimeFunction;
import com.evil.common.rocketmq.constant.MQConstant;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 机器人服务
 *
 * @author liyang
 * @date 2022-08-08 00:00
 */
@Slf4j
@RestController
@RequiredArgsConstructor
public class RobotActionServiceImpl implements RobotActionService, RemoteRobotActionService {

    private final RedisUtil redisUtil;

    private final LoginUtil loginUtil;

    private final ReTriggerUtil reTriggerUtil;

    private final MatchingUtil matchingUtil;

    private final ApplicationOrderService applicationOrderService;

    private final ApplicationOrderDataService applicationOrderDataService;

    private final ElasticsearchService elasticsearchService;

    private final RocketMQTemplate rocketMQTemplate;

    private final ApplicationRobotLogService applicationRobotLogService;

    /**
     * 执行机器人 （执行前日志记录）
     *
     * @param robotActionReqDTO robotActionReqDTO
     */
    @Override
    public List<ControlDataDTO> robotActionBeforeLog(RobotActionReqDTO robotActionReqDTO) {
        // 机器人日志
        Long logId = applicationRobotLogService.insertRobotLog(robotActionReqDTO);
        robotActionReqDTO.setLogId(logId);
        if (robotActionReqDTO instanceof RobotActionMQReqDTO) {
            this.robotActionMq((RobotActionMQReqDTO) robotActionReqDTO);
        }
        return this.robotActionErrorHandle(robotActionReqDTO);
    }

    /**
     * 执行机器人-判断处理队列方式
     *
     * @param robotActionMQReqDTO robotActionMQReqDTO
     */
    @Override
    public List<ControlDataDTO> robotActionMq(RobotActionMQReqDTO robotActionMQReqDTO) {
        if (SwitchEnum.isOpen(robotActionMQReqDTO.getSync())) {
            // 同步版本
            return this.robotActionErrorHandle(robotActionMQReqDTO);
        } else {
            // 队列版本
            this.convertAndSend(robotActionMQReqDTO);
            return new ArrayList<>();
        }
    }

    @Override
    @ExecuteTime(name = "【robotActionErrorHandle】")
    @GlobalTransactional(rollbackFor = Exception.class)
    public List<ControlDataDTO> robotActionErrorHandle(@Validated RobotActionReqDTO robotActionReqDTO) {
        if (null == robotActionReqDTO.getLogId()) {
            log.error("【机器人】，异常：logId为空");
            throw new BusinessException(RCodeEnum.ERROR_RUNTIME);
        }
        RobotActionResultEnum resultEnum = RobotActionResultEnum.FAILED;
        try {
            List<ControlDataDTO> controlDataList = this.robotAction(robotActionReqDTO);
            resultEnum = RobotActionResultEnum.SUCCEED;
            return controlDataList;
        } catch (Exception e) {
            log.error("【机器人】，执行失败，机器人信息：{}，失败原因：{}", robotActionReqDTO.info(), e);
            if (e instanceof BusinessException) {
                log.error("【机器人】，执行失败，BusinessException.Data：{}", ((BusinessException) e).getData());
            }

            String robotAddOrderIdKey = ApplicationRedisKeyUtil.requestUuidRobotAddOrderId();
            String robotModifyOrderIdKey = ApplicationRedisKeyUtil.requestUuidRobotModifyOrderId();
            Set<Object> adds = redisUtil.sGet(robotAddOrderIdKey);
            Set<Object> modifies = redisUtil.sGet(robotModifyOrderIdKey);

            if (CollectionUtil.isNotEmpty(adds)) {
                log.info("【机器人】，执行失败，移除es订单，{}", adds);
                elasticsearchService.delete(StreamUtil.transListT(adds, o -> (Long) o));
                log.info("【机器人】，执行失败，移除redis，robotAddOrderId数据，key:{}", robotAddOrderIdKey);
                redisUtil.del(robotAddOrderIdKey);
            }
            if (CollectionUtil.isNotEmpty(modifies)) {
                log.info("【机器人】，执行失败，刷新es订单，{}", modifies);
                applicationOrderDataService.refreshEsOrderIds(StreamUtil.transListTF(modifies, o -> (Long) o, o -> !adds.contains(o)));
                log.info("【机器人】，执行失败，移除redis数据，robotModifyOrderId数据，key:{}", robotModifyOrderIdKey);
                redisUtil.del(robotModifyOrderIdKey);
            }
            throw e;
        } finally {
            applicationRobotLogService.updateRobotStatus(robotActionReqDTO.getLogId(), resultEnum);
        }
    }

    /**
     * 执行机器人
     *
     * @param robotActionReqDTO robotActionReqDTO
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @ExecuteTime(name = "【robotAction】")
    public List<ControlDataDTO> robotAction(@Validated RobotActionReqDTO robotActionReqDTO) {
        log.info("【机器人】，{}", robotActionReqDTO.info());
        Long orderId = robotActionReqDTO.getOrderId();
        Long targetAppId = robotActionReqDTO.getTargetAppId();
        List<JudgeOr> judgeOrs = robotActionReqDTO.getJudgeOrs();
        // 是否开启重新运算公式
        List<Long> recalculationControlIds = SwitchEnum.isOpen(robotActionReqDTO.getRecalculation()) ?
                robotActionReqDTO.getRecalculationControlIds() : new ArrayList<>();
        UserEnterpriseDTO handler = null != robotActionReqDTO.getHandler() ? robotActionReqDTO.getHandler() : loginUtil.getLoginUserEnterprise();

        // 目标应用控件map
        ExecuteTimeFunction start = ExecuteTimeFunction.start();
        InitControlDataRespDTO targetControlDataDTO = applicationOrderDataService.initAppOrderData(targetAppId);
        start.log("【机器人-获取初始化应用订单数据】");

        // 获取订单最新数据
        start.reset();
        List<ControlDataDTO> controlDataList = elasticsearchService.orderDataList(orderId);
        start.log("【机器人-获取订单最新数据】");
        // 订单数据map
        Map<Long, ControlDataDTO> sourceDataMap = StreamUtil.toMapK(controlDataList, ControlDataDTO::getControlId);
        // 首次触发类型
        RobotTriggerTypeEnum robotTriggerTypeEnum = RobotTriggerTypeEnum.isExistById(robotActionReqDTO.getTriggerType());
        // 获取过滤规则
        Function<RobotTriggerTypeEnum, List<RobotRuleDTO>> filterRule = typeEnum -> robotActionReqDTO.getRobotRules().stream()
                .filter(e -> typeEnum.getId() == e.getRobotRuleType()).collect(Collectors.toList());

        Callback robotActionAddOrder = () -> {
            // 新增规则
            List<RobotRuleDTO> robotRules = filterRule.apply(RobotTriggerTypeEnum.ADD);
            // 先匹配规则
            start.reset();
            this.ruleMatching(robotRules, new ArrayList<>(), sourceDataMap, targetControlDataDTO);
            start.log("【机器人-新增数据-匹配规则】");
            // 再重新运算公式
            start.reset();
            reTriggerUtil.reTrigger(targetControlDataDTO.getControlMap(), targetControlDataDTO.getControlDataMap(), recalculationControlIds);
            start.log("【机器人-新增数据-重新运算公式】");

            if (robotActionReqDTO.getSaveNow()) {
                AddOrderReqDTO addOrderReqDTO = new AddOrderReqDTO();
                addOrderReqDTO.setApplicationKey(targetControlDataDTO.getApplicationKey());
                addOrderReqDTO.setData(targetControlDataDTO.getControlDataList());
                addOrderReqDTO.setOrderSourceType(OrderSourceTypeEnum.ROBOT_ADD_ORDER.getId());
                addOrderReqDTO.setOrderProcessStatus(OrderProcessStatusEnum.PROCESSING.getId());
                addOrderReqDTO.setHandler(handler);
                addOrderReqDTO.setLogAppKeys(robotActionReqDTO.getLogAppKeys());
                AddOrderRespDTO addOrderRespDTO = applicationOrderService.addOrder(addOrderReqDTO);

                // 机器人日志 （添加数据 只有成功时才有日志详情）
                Long robotLogId = (Long) redisUtil.get(ApplicationRedisKeyUtil.requestUuidRobotLogId());
                if (Objects.nonNull(robotLogId)) {
                    ApplicationRobotLogDetail robotLogDetail = this.initRobotLogDetail(robotLogId, addOrderRespDTO.getOrderId(), addOrderRespDTO.getOrderSn());
                    robotLogDetail.setActionResult(RobotActionResultEnum.SUCCEED.getId());
                    this.addRobotLogDetail(robotLogDetail);
                }
            }
        };

        switch (robotTriggerTypeEnum) {
            case ADD:
                // 新增数据
                robotActionAddOrder.apply();
                return targetControlDataDTO.getControlDataList();
            case MODIFY:
                // 更新数据
                OrderPageReqDTO orderPageReqDTO = new OrderPageReqDTO();
                orderPageReqDTO.setApplicationId(targetAppId);
                // 处理条件中匹配字段
                OrderDataUtil.handleMatchingCondition(judgeOrs, sourceDataMap, matchingUtil::dynamic);
                orderPageReqDTO.setJudgeOrs(judgeOrs);
                start.reset();
                List<OrderPageDataRespDTO> orders = elasticsearchService.orders(orderPageReqDTO);
                start.log("【机器人-更新数据-搜索引擎获取数据】");

                if (orders.isEmpty()) {
                    // 找不到符合筛选条件的数据
                    if (robotActionReqDTO.unConditionAdd()) {
                        // 若找不到符合筛选条件的数据，则根据以下规则在目标应用中添加一条数据
                        robotActionAddOrder.apply();
                    }
                } else {
                    // 匹配规则控件值
                    Map<Long, List<ControlDataDTO>> ordersDataListMap = new HashMap<>();
                    List<ApplicationRobotLogDetail> robotLogDetails = new ArrayList<>();
                    // 更新规则
                    List<RobotRuleDTO> robotRules = filterRule.apply(RobotTriggerTypeEnum.MODIFY);
                    start.reset();
                    orders.forEach(order -> {
                        Function<ControlDataDTO, ControlDataDTO> copy = e -> e.copy(true);
                        Map<Long, ControlDataDTO> targetDataMap = StreamUtil.toMapKV(order.getData(), ControlDataDTO::getControlId, copy);
                        targetControlDataDTO.setControlDataMap(targetDataMap);
                        // 先匹配规则
                        start.reset();
                        this.ruleMatching(robotRules, judgeOrs, sourceDataMap, targetControlDataDTO);
                        start.log("【机器人-更新数据-匹配规则】");
                        targetDataMap = targetControlDataDTO.getControlDataMap();
                        // 再重新运算公式
                        start.reset();
                        reTriggerUtil.reTrigger(targetControlDataDTO.getControlMap(), targetDataMap, recalculationControlIds);
                        start.log("【机器人-更新数据-重新运算公式】");
                        // 记录要修改的订单数据
                        List<ControlDataDTO> changeList = OrderDataUtil.getChangeList(targetDataMap.values(), order.getData());
                        ordersDataListMap.put(order.getOrderId(), changeList);
                        // 机器人日志

                        // 机器人日志 （添加数据 只有成功时才有日志详情）
                        Long robotLogId = (Long) redisUtil.get(ApplicationRedisKeyUtil.requestUuidRobotLogId());
                        if (Objects.nonNull(robotLogId)) {
                            robotLogDetails.add(this.initRobotLogDetail(robotLogId, order.getOrderId(), order.getOrderSn()));
                        }
                    });
                    start.log("【机器人-更新数据-处理订单总耗时】");

                    try {
                        // 批量更改订单数据
                        AppOrdersControlDataDTO appOrdersControlDataDTO = new AppOrdersControlDataDTO();
                        appOrdersControlDataDTO.setApplicationKey(targetControlDataDTO.getApplicationKey());
                        appOrdersControlDataDTO.setOrdersDataListMap(ordersDataListMap);
                        appOrdersControlDataDTO.setLogAppKeys(robotActionReqDTO.getLogAppKeys());
                        applicationOrderService.modifyOrderBatch(appOrdersControlDataDTO);

                        // 日志详情-成功
                        robotLogDetails.forEach(detail -> {
                            detail.setActionResult(RobotActionResultEnum.SUCCEED.getId());
                            this.addRobotLogDetail(detail);
                        });
                    } catch (Exception e) {
                        // 日志详情-失败
                        robotLogDetails.forEach(detail -> {
                            detail.setActionResult(RobotActionResultEnum.FAILED.getId());
                            this.addRobotLogDetail(detail);
                        });
                        throw e;
                    }
                }
                return new ArrayList<>();
            default:
                throw RobotTriggerTypeEnum.getBusinessException();
        }
    }

    /**
     * 发起执行机器人队列
     *
     * @param robotActionMQReqDTO robotActionMQReqDTO
     */
    @Override
    public void convertAndSend(RobotActionMQReqDTO robotActionMQReqDTO) {
        robotActionMQReqDTO.setEnterpriseId(loginUtil.getLoginEnterpriseId());
        // 发起队列
        rocketMQTemplate.convertAndSend(MQConstant.APPLICATION_ROBOT_MQ_KEY, robotActionMQReqDTO);
    }

    /**
     * 构建 机器人日志内容
     *
     * @param robotLogId robotLogId
     * @param orderId    orderId
     * @param orderSn    orderSn
     * @return ApplicationRobotLogDetail
     */
    private ApplicationRobotLogDetail initRobotLogDetail(Long robotLogId, Long orderId, String orderSn) {
        UserBaseRespDTO loginUser = loginUtil.getLoginUserBase();
        ApplicationRobotLogDetail robotLogDetail = new ApplicationRobotLogDetail();
        robotLogDetail.setRobotLogId(robotLogId);
        robotLogDetail.setActionTargetId(orderId);
        robotLogDetail.setActionTargetSn(orderSn);
        robotLogDetail.setCreatorId(loginUser.getUserId());
        robotLogDetail.setCreatorName(loginUser.getNickName());

        return robotLogDetail;
    }

    /**
     * 添加机器人日志内容：目前使用暂存redis
     *
     * @param robotLogDetail robotLogDetail
     */
    private void addRobotLogDetail(ApplicationRobotLogDetail robotLogDetail) {
        redisUtil.sSetAndTime(ApplicationRedisKeyUtil.robotLogKey(robotLogDetail.getRobotLogId()), ApplicationRedisConstant.ROBOT_KEY_EXPIRE_IN_SECOND, robotLogDetail);
    }

    /**
     * 匹配规则
     *
     * @param robotRules    robotRules
     * @param judgeOrs      judgeOrs
     * @param sourceDataMap sourceDataMap
     * @param targetDataDTO targetDataDTO
     */
    private void ruleMatching(List<RobotRuleDTO> robotRules, Collection<JudgeOr> judgeOrs, Map<Long, ControlDataDTO> sourceDataMap, OrderControlDataRespDTO targetDataDTO) {
        OrderDataUtil.ruleMatching(robotRules, judgeOrs, sourceDataMap, targetDataDTO, ApplicationUtil::controlToControlData, matchingUtil::dynamic, matchingUtil::dynamic);
    }
}
